# Copyright (C) 2015, Wazuh Inc.
# Created by Wazuh, Inc. <info@wazuh.com>.
# This program is free software; you can redistribute it and/or modify it under the terms of GPLv2

import hashlib
import operator
from os import chmod, path, listdir
from typing import Union

from wazuh.core import common, configuration
from wazuh.core.InputValidator import InputValidator
from wazuh.core.agent import WazuhDBQueryAgents, WazuhDBQueryGroupByAgents, WazuhDBQueryMultigroups, Agent, \
    WazuhDBQueryGroup, create_upgrade_tasks, get_agents_info, get_groups, get_rbac_filters, send_restart_command, \
    GROUP_FIELDS, GROUP_REQUIRED_FIELDS, GROUP_FILES_FIELDS, GROUP_FILES_REQUIRED_FIELDS
from wazuh.core.cluster.cluster import get_node
from wazuh.core.cluster.utils import read_cluster_config
from wazuh.core.exception import WazuhError, WazuhInternalError, WazuhException, WazuhResourceNotFound
from wazuh.core.results import WazuhResult, AffectedItemsWazuhResult
from wazuh.core.utils import chmod_r, chown_r, get_hash, mkdir_with_mode, md5, process_array, clear_temporary_caches, \
    full_copy, check_if_wazuh_agent_version, parse_wazuh_agent_version
from wazuh.core.wazuh_queue import WazuhQueue
from wazuh.rbac.decorators import expose_resources

cluster_enabled = not read_cluster_config(from_import=True)['disabled']
node_id = get_node().get('node') if cluster_enabled else None

UPGRADE_CHUNK_SIZE = 500
UPGRADE_RESULT_CHUNK_SIZE = 97

# Error codes generated from upgrade socket error codes that should be excluded in upgrade functions
# 1819 -> The WPK for this platform is not available
# 1820 -> Upgrade procedure could not start. Agent already upgrading
# 1821 -> Remote upgrade is not available for this agent version
# 1822 -> Current agent version is greater or equal
ERROR_CODES_UPGRADE_SOCKET = [1819, 1820, 1821, 1822]

# Error codes generated from upgrade socket error codes that should be raised as bad request (400)
# The rest of codes generated by the socket will be raised as Wazuh internal errors (500)
# 1823 -> Upgrading an agent to a version higher than the manager requires the force flag
ERROR_CODES_UPGRADE_SOCKET_BAD_REQUEST = [1823]

# Error codes generated from upgrade socket error codes that should be excluded in get upgrade results
# 1813 -> No task in DB
ERROR_CODES_UPGRADE_SOCKET_GET_UPGRADE_RESULT = [1813]


@expose_resources(actions=["agent:read"], resources=["agent:id:{agent_list}"], post_proc_func=None)
def get_distinct_agents(agent_list: list = None, offset: int = 0, limit: int = common.DATABASE_LIMIT, sort: dict = None,
                        search: dict = None, fields: list = None, q: str = None) -> AffectedItemsWazuhResult:
    """Get all the different combinations that all system agents have for the selected fields. It also indicates the
    total number of agents that have each combination.

    Parameters
    ----------
    agent_list : list
        List of agents ID's.
    fields : list
        List of fields to group by.
    offset : int
        First item to return.
    limit : int
        Maximum number of items to return.
    sort : dict
        Sorts the items. Format: {"fields":["field1","field2"],"order":"asc|desc"}.
    search : dict
        Looks for items with the specified string. Format: {"fields": ["field1","field2"]}.
    q : str
        Query to filter results by. For example q&#x3D;&amp;quot;status&#x3D;active&amp;quot;

    Returns
    -------
    AffectedItemsWazuhResult
        Affected items.
    """

    result = AffectedItemsWazuhResult(all_msg='All selected agents information was returned',
                                      some_msg='Some agents information was not returned',
                                      none_msg='No agent information was returned'
                                      )

    if agent_list:
        rbac_filters = get_rbac_filters(system_resources=get_agents_info(), permitted_resources=agent_list)

        with WazuhDBQueryGroupByAgents(filter_fields=fields, select=fields, offset=offset, limit=limit, sort=sort,
                                       search=search, query=q, min_select_fields=set(), count=True,
                                       get_data=True, **rbac_filters) as db_query:
            data = db_query.run()

        result.affected_items.extend(data['items'])
        result.total_affected_items = data['totalItems']

    return result


@expose_resources(actions=["agent:read"], resources=["agent:id:{agent_list}"], post_proc_func=None)
def get_agents_summary_status(agent_list: list[str] = None) -> WazuhResult:
    """Count the number of agents by connection and groups configuration synchronization statuses.

    Parameters
    ----------
    agent_list : list[str]
       List of agents ID's

    Returns
    -------
    WazuhResult
        WazuhResult object.
    """
    connection = {'active': 0, 'disconnected': 0, 'never_connected': 0, 'pending': 0, 'total': 0}
    sync_configuration = {'synced': 0, 'not synced': 0, 'total': 0}
    if agent_list:
        rbac_filters = get_rbac_filters(system_resources=get_agents_info(), permitted_resources=agent_list)

        # We don't consider agent 000 in order to get the summary
        with WazuhDBQueryAgents(limit=None, select=['status', 'group_config_status'], query="id!=000",
                                **rbac_filters) as db_query:
            data = db_query.run()

        items = data['items']
        for agent in items:
            connection[agent['status']] += 1
            sync_configuration[agent['group_config_status']] += 1

        connection['total'] = sync_configuration['total'] = len(items)

    sync_configuration['not_synced'] = sync_configuration.pop('not synced')
    return WazuhResult({'data': {'connection': connection, 'configuration': sync_configuration}})


@expose_resources(actions=["agent:read"], resources=["agent:id:{agent_list}"], post_proc_func=None)
def get_agents_summary_os(agent_list: list[str] = None) -> AffectedItemsWazuhResult:
    """Get a list of available OS.

    Parameters
    ----------
    agent_list : list[str]
       List of agents ID's

    Returns
    -------
    AffectedItemsWazuhResult
        Affected items.
    """
    result = AffectedItemsWazuhResult(none_msg='Could not get the operative system of the agents',
                                      all_msg='Showing the operative system of all specified agents',
                                      some_msg='Could not get the operative system of some agents')
    if agent_list:
        rbac_filters = get_rbac_filters(system_resources=get_agents_info(), permitted_resources=agent_list)

        # We don't consider agent 000 in order to get the summary
        with WazuhDBQueryAgents(select=['os.platform'], default_sort_field='os_platform', min_select_fields=set(),
                                distinct=True, query="id!=000", **rbac_filters) as db_query:
            query_data = db_query.run()

        query_data['items'] = [row['os']['platform'] for row in query_data['items']]
        result.affected_items = query_data['items']
        result.total_affected_items = len(result.affected_items)

    return result


@expose_resources(actions=["agent:reconnect"], resources=["agent:id:{agent_list}"],
                  post_proc_kwargs={'exclude_codes': [1701, 1703, 1707]})
def reconnect_agents(agent_list: Union[list, str] = None) -> AffectedItemsWazuhResult:
    """Force reconnect a list of agents.

    Parameters
    ----------
    list or str
        List of agent IDs. All possible values from 000 onwards. Default `*`

    Returns
    -------
    AffectedItemsWazuhResult
        Affected items.
    """
    result = AffectedItemsWazuhResult(all_msg='Force reconnect command was sent to all agents',
                                      some_msg='Force reconnect command was not sent to some agents',
                                      none_msg='Force reconnect command was not sent to any agent'
                                      )

    system_agents = get_agents_info()
    with WazuhQueue(common.AR_SOCKET) as wq:
        for agent_id in agent_list:
            try:
                if agent_id not in system_agents:
                    raise WazuhResourceNotFound(1701)
                if agent_id == "000":
                    raise WazuhError(1703)
                Agent(agent_id).reconnect(wq)
                result.affected_items.append(agent_id)
            except WazuhException as e:
                result.add_failed_item(id_=agent_id, error=e)

    result.total_affected_items = len(result.affected_items)
    result.affected_items.sort(key=int)

    return result


@expose_resources(actions=["agent:restart"], resources=["agent:id:{agent_list}"],
                  post_proc_kwargs={'exclude_codes': [1701, 1703, 1707]})
def restart_agents(agent_list: list = None) -> AffectedItemsWazuhResult:
    """Restart a list of agents.

    Parameters
    ----------
    agent_list : list
        List of agents IDs.

    Returns
    -------
    AffectedItemsWazuhResult
        Affected items.
    """
    result = AffectedItemsWazuhResult(all_msg='Restart command was sent to all agents',
                                      some_msg='Restart command was not sent to some agents',
                                      none_msg='Restart command was not sent to any agent'
                                      )

    agent_list = set(agent_list)

    # Add agent with ID 000 to failed_items
    try:
        agent_list.remove('000')
        result.add_failed_item('000', WazuhError(1703))
    except KeyError:
        pass

    if agent_list:
        system_agents = get_agents_info()
        rbac_filters = get_rbac_filters(system_resources=system_agents, permitted_resources=list(agent_list))
        with WazuhDBQueryAgents(limit=None, select=["id", "status", "version"], **rbac_filters) as query_data:
            agents_with_data = query_data.run()['items']

        # Add non existent agents to failed_items
        not_found_agents = agent_list - system_agents
        [result.add_failed_item(id_=agent, error=WazuhResourceNotFound(1701)) for agent in not_found_agents]

        # Add non active agents to failed_items
        non_active_agents = [agent for agent in agents_with_data if agent['status'] != 'active']
        [result.add_failed_item(id_=agent['id'], error=WazuhError(1707))
         for agent in non_active_agents]

        eligible_agents = [agent for agent in agents_with_data if agent not in non_active_agents] if non_active_agents \
            else agents_with_data
        with WazuhQueue(common.AR_SOCKET) as wq:
            for agent in eligible_agents:
                try:
                    send_restart_command(agent['id'], agent['version'], wq)
                    result.affected_items.append(agent['id'])
                except WazuhException as e:
                    result.add_failed_item(id_=agent['id'], error=e)

        result.total_affected_items = len(result.affected_items)
        result.affected_items.sort(key=int)

    return result


@expose_resources(actions=['cluster:read'], resources=[f'node:id:{node_id}'],
                  post_proc_kwargs={'exclude_codes': [1701, 1703, 1707], 'force': True})
def restart_agents_by_node(agent_list: list = None) -> AffectedItemsWazuhResult:
    """Restart all agents belonging to a node.

    Parameters
    ----------
    agent_list : list, optional
        List of agents. Default `None`

    Returns
    -------
    AffectedItemsWazuhResult
        Affected items.
    """
    '000' in agent_list and agent_list.remove('000')
    return restart_agents(agent_list=agent_list)


@expose_resources(actions=["agent:read"], resources=["agent:id:{agent_list}"],
                  post_proc_kwargs={'exclude_codes': [1701, 1703, 1707], 'force': True})
def restart_agents_by_group(agent_list: list = None) -> AffectedItemsWazuhResult:
    """Restart all agents belonging to a group.

    Parameters
    ----------
    agent_list : list, optional
        List of agents. Default `None`

    Returns
    -------
    AffectedItemsWazuhResult
        Affected items.
    """
    return restart_agents(agent_list=agent_list)


@expose_resources(actions=["agent:read"], resources=["agent:id:{agent_list}"],
                  post_proc_kwargs={'exclude_codes': [1701]})
def get_agents(agent_list: list = None, offset: int = 0, limit: int = common.DATABASE_LIMIT, sort: dict = None,
               search: dict = None, select: dict = None, filters: dict = None,
               q: str = None, distinct: bool = False) -> AffectedItemsWazuhResult:
    """Gets a list of available agents with basic attributes.

    Parameters
    ----------
    agent_list : list
        List of agents IDs.
    offset : int
        First element to return in the collection.
    limit : int
        Maximum number of elements to return. Default: common.DATABASE_LIMIT
    select : dict
        Select fields to return. Format: {"fields":["field1","field2"]}.
    sort : dict
        Sorts the items. Format: {"fields":["field1","field2"],"order":"asc|desc"}.
    search : dict
        Look for elements with the specified string. Format: {"fields": ["field1","field2"]}
    filters : dict
        Defines required field filters. Format: {"field1":"value1", "field2":["value2","value3"]}
    q : str
        Query to filter results by.
    distinct : bool
        Look for distinct values.

    Returns
    -------
    AffectedItemsWazuhResult
        Affected items.
    """
    result = AffectedItemsWazuhResult(all_msg='All selected agents information was returned',
                                      some_msg='Some agents information was not returned',
                                      none_msg='No agent information was returned'
                                      )
    if agent_list:
        if filters is None:
            filters = dict()

        system_agents = get_agents_info()

        for agent_id in agent_list:
            if agent_id not in system_agents:
                result.add_failed_item(id_=agent_id, error=WazuhResourceNotFound(1701))

        rbac_filters = get_rbac_filters(system_resources=system_agents, permitted_resources=agent_list, filters=filters)

        with WazuhDBQueryAgents(offset=offset, limit=limit, sort=sort, search=search, select=select,
                                query=q, **rbac_filters, distinct=distinct) as db_query:
            data = db_query.run()

        if sort and 'version' in sort['fields']:
            data['items'] = sorted(data['items'],
                          key=lambda o: tuple(
                              parse_wazuh_agent_version(o.get(a)) if a == 'version' and check_if_wazuh_agent_version(o.get(a))
                              else (0, 0, 0) if o.get(a) is None and a == 'version'
                              else o.get(a).lower() if type(o.get(a)) == str else o.get(a) for a in sort['fields']),
                          reverse=False if sort['order'] == 'asc' else True)

        result.affected_items.extend(data['items'])
        result.total_affected_items = data['totalItems']

    return result


@expose_resources(actions=["group:read"], resources=["group:id:{group_list}"], post_proc_func=None)
def get_agents_in_group(group_list: list, offset: int = 0, limit: int = common.DATABASE_LIMIT, sort: dict = None,
                        search: dict = None, select: dict = None, filters: dict = None,
                        q: str = None, distinct: bool = False) -> AffectedItemsWazuhResult:
    """Gets a list of available agents with basic attributes.

    Parameters
    ----------
    group_list : list
        List containing the group ID.
    offset : int
        First element to return in the collection.
    limit : int
        Maximum number of elements to return. Default: common.DATABASE_LIMIT
    select : dict
        Select fields to return. Format: {"fields":["field1","field2"]}.
    sort : dict
        Sorts the items. Format: {"fields":["field1","field2"],"order":"asc|desc"}.
    search : dict
        Look for elements with the specified string. Format: {"fields": ["field1","field2"]}
    filters : dict
        Defines required field filters. Format: {"field1":"value1", "field2":["value2","value3"]}
    q : str
        Query to filter results by.
    distinct : bool
        Look for distinct values.

    Raises
    ------
    WazuhResourceNotFound(1710)
        If the group does not exist.

    Returns
    -------
    AffectedItemsWazuhResult
        Affected items.
    """
    system_groups = get_groups()

    if group_list[0] not in system_groups:
        raise WazuhResourceNotFound(1710)

    q_group = f'group={group_list[0]}'
    q = f'{q_group};({q})' if q else q_group

    return get_agents(offset=offset, limit=limit, sort=sort, search=search, select=select, filters=filters, q=q,
     distinct=distinct)


@expose_resources(actions=["agent:read"], resources=["agent:id:{agent_list}"],
                  post_proc_kwargs={'exclude_codes': [1701]})
def get_agents_keys(agent_list: list = None) -> AffectedItemsWazuhResult:
    """Get the key of existing agents.

    Parameters
    ----------
    agent_list : list
        List of agents ID's.

    Returns
    -------
    AffectedItemsWazuhResult
        Affected items.
    """
    result = AffectedItemsWazuhResult(all_msg='Obtained keys for all selected agents',
                                      some_msg='Some agent keys were not obtained',
                                      none_msg='No agent keys were obtained'
                                      )
    system_agents = get_agents_info()
    for agent_id in agent_list:
        try:
            if agent_id not in system_agents:
                raise WazuhResourceNotFound(1701)
            result.affected_items.append({'id': agent_id, 'key': Agent(agent_id).get_key()})
        except WazuhException as e:
            result.add_failed_item(id_=agent_id, error=e)
    result.total_affected_items = len(result.affected_items)
    result.affected_items.sort(key=lambda i: i['id'])

    return result


@expose_resources(actions=["agent:delete"], resources=["agent:id:{agent_list}"],
                  post_proc_kwargs={'exclude_codes': [1701, 1703, 1731]})
def delete_agents(agent_list: list = None, purge: bool = False, filters: dict = None,
                  q: str = None) -> AffectedItemsWazuhResult:
    """Delete a list of agents.

    Parameters
    ----------
    agent_list : list
        List of agents ID's to be deleted.
    purge : bool
        Delete definitely from key store.
    filters : dict
        Define required field filters. Format: {"field1":"value1", "field2":["value2","value3"]}
    q : str
        Define query to filter in DB.

    Raises
    ------
    WazuhError(1726)
        Authd is not running.

    Returns
    -------
    AffectedItemsWazuhResult
        Result with affected agents.
    """
    result = AffectedItemsWazuhResult(all_msg='All selected agents were deleted',
                                      some_msg='Some agents were not deleted',
                                      none_msg='No agents were deleted'
                                      )
    if agent_list:
        system_agents = get_agents_info()
        rbac_filters = get_rbac_filters(system_resources=system_agents, permitted_resources=agent_list,
                                        filters=filters)

        with WazuhDBQueryAgents(limit=None, select=["id"], query=q, **rbac_filters) as db_query:
            data = db_query.run()

        can_purge_agents = set(map(operator.itemgetter('id'), data['items']))
        agent_list = set(agent_list)

        try:
            agent_list.remove('000')
            result.add_failed_item('000', WazuhError(1703))
        except KeyError:
            pass

        # Add not existing agents to failed_items
        not_found_agents = agent_list - system_agents
        list(map(lambda ag: result.add_failed_item(id_=ag, error=WazuhResourceNotFound(1701)), not_found_agents))

        # Add non eligible agents to failed_items
        non_eligible_agents = agent_list - not_found_agents - can_purge_agents
        list(map(lambda ag: result.add_failed_item(id_=ag, error=WazuhError(
            1731,
            extra_message="some of the requirements are not met -> {}".format(
                ', '.join(f"{key}: {value}" for key, value in filters.items() if key != 'rbac_ids') +
                (f', q: {q}' if q else '')
            )
        )), non_eligible_agents))

        for agent_id in agent_list.intersection(system_agents).intersection(can_purge_agents):
            try:
                my_agent = Agent(agent_id)
                my_agent.remove(purge=purge)
                result.affected_items.append(agent_id)
            except WazuhError as e:
                if e.code == 1726:
                    raise e

                result.add_failed_item(id_=agent_id, error=e)

        # Clear temporary cache
        clear_temporary_caches()

        result.total_affected_items = len(result.affected_items)
        result.affected_items.sort(key=int)

    return result


@expose_resources(actions=["agent:create"], resources=["*:*:*"], post_proc_func=None)
def add_agent(name: str = None, agent_id: str = None, key: str = None, ip: str = 'any',
              force: dict = None) -> WazuhResult:
    """Add a new Wazuh agent.

    Parameters
    ----------
    name : str
        Name of the new agent.
    agent_id : str
        ID of the new agent.
    key : str
        Key of the new agent.
    ip : str
        IP of the new agent. It can be an IP, IP/NET or "any".
    force : dict
        Remove old agent with the same name or IP if conditions are met.

    Raises
    ------
    WazuhError(1738)
        Name length is greater than 128 characters.

    Returns
    -------
    WazuhResult
        Added agent information.
    """
    # Check length of agent name
    if len(name) > common.AGENT_NAME_LEN_LIMIT:
        raise WazuhError(1738)

    new_agent = Agent(name=name, ip=ip, id=agent_id, key=key, force=force)

    return WazuhResult({'data': {'id': new_agent.id, 'key': new_agent.key}})


@expose_resources(actions=["group:read"], resources=["group:id:{group_list}"],
                  post_proc_kwargs={'exclude_codes': [1710]})
def get_agent_groups(group_list: list = None, offset: int = 0, limit: int = None, sort_by: list = None,
                     sort_ascending: bool = True, search_text: str = None, complementary_search: bool = False,
                     hash_algorithm: str = 'md5', q: str = None, select: str = None,
                     distinct: bool = False) -> AffectedItemsWazuhResult:
    """Gets the existing groups.

    Parameters
    ----------
    group_list : list
        List of group names.
    offset : int
        First element to return in the collection.
    limit : int
        Maximum number of elements to return. Default: common.DATABASE_LIMIT
    sort_by : list
        Fields to sort the items by.
    sort_ascending : bool
        Sort in ascending (true) or descending (false) order. Default: True
    search_text : str
        Text to search.
    complementary_search : bool
        Find items without the text to search. Default: False
    hash_algorithm : str
        hash algorithm used to get mergedsum and configsum. Default: 'md5'
    q : str
        Query to filter results by.
    select : str
        Select which fields to return (separated by comma).
    distinct : bool
        Look for distinct values.

    Returns
    -------
    AffectedItemsWazuhResult
        Affected items.
    """
    affected_groups = list()
    result = AffectedItemsWazuhResult(all_msg='All selected groups information was returned',
                                      some_msg='Some groups information was not returned',
                                      none_msg='No group information was returned'
                                      )

    if group_list:
        system_groups = get_groups()
        # Add failed items
        for invalid_group in set(group_list) - system_groups:
            result.add_failed_item(id_=invalid_group, error=WazuhResourceNotFound(1710))

        rbac_filters = get_rbac_filters(system_resources=system_groups, permitted_resources=group_list)

        with WazuhDBQueryGroup(**rbac_filters, limit=None) as group_query:
            query_data = group_query.run()

            for group in query_data['items']:
                if group_list and group['name'] not in group_list:
                    continue

                full_entry = path.join(common.SHARED_PATH, group['name'])

                # merged.mg and agent.conf sum
                merged_sum = get_hash(path.join(full_entry, "merged.mg"), hash_algorithm)
                if merged_sum:
                    group['mergedSum'] = merged_sum

                conf_sum = get_hash(path.join(full_entry, "agent.conf"), hash_algorithm)
                if conf_sum:
                    group['configSum'] = conf_sum

                affected_groups.append(group)

        data = process_array(affected_groups, offset=offset, limit=limit, allowed_sort_fields=GROUP_FIELDS,
                            sort_by=sort_by, sort_ascending=sort_ascending, search_text=search_text,
                            complementary_search=complementary_search, q=q, allowed_select_fields=GROUP_FIELDS,
                            select=select, distinct=distinct, required_fields=GROUP_REQUIRED_FIELDS)
        result.affected_items = data['items']
        result.total_affected_items = data['totalItems']

    return result


@expose_resources(actions=["group:read"], resources=["group:id:{group_list}"], post_proc_func=None)
def get_group_files(group_list: list = None, offset: int = 0, limit: int = None, search_text: str = None,
                    search_in_fields: list = None, complementary_search: bool = False, sort_by: list = None,
                    sort_ascending: bool = True, hash_algorithm: str = 'md5', q: str = None,
                    select: str = None, distinct: bool = False) -> WazuhResult:
    """Gets the group files.

    Parameters
    ----------
    group_list : list
        List of Group names.
    search_text : str
        Text to search.
    complementary_search : bool
        Find items without the text to search. Default: False
    search_in_fields : list
        Fields to search in.
    sort_by : list
        Fields to sort the items by.
    sort_ascending : bool
        Sort in ascending (true) or descending (false) order. Default: True
    hash_algorithm : str
        Hash algorithm used to get mergedsum and configsum. Default: 'md5'
    offset : int
        First element to return.
    limit : int
        Maximum number of elements to return
    q : str
        Query to filter results by.
    select : str
        Select which fields to return (separated by comma).
        Maximum number of elements to return.
    distinct : bool
        Look for distinct values.

    Raises
    ------
    WazuhInternalError(1727)
        If there was an error listing group files.
    WazuhError
        Generic error.

    Returns
    -------
    WazuhResult
        WazuhResult object with the groups files.
    """
    # We access unique group_id from list, this may change if and when we decide to add option to get files for
    # a list of groups
    group_id = group_list[0]
    group_path = common.SHARED_PATH
    result = AffectedItemsWazuhResult(all_msg='All selected groups files were returned',
                                      some_msg='Some groups files were not returned',
                                      none_msg='No groups files were returned'
                                      )
    if group_id:
        if not Agent.group_exists(group_id):
            result.add_failed_item(id_=group_id, error=WazuhResourceNotFound(1710))
            return result
        group_path = path.join(common.SHARED_PATH, group_id)

    if not path.exists(group_path):
        result.add_failed_item(id_=group_path, error=WazuhError(1006))
        return result

    try:
        data = []
        for entry in listdir(group_path):
            item = dict()
            item['filename'] = entry
            item['hash'] = get_hash(path.join(group_path, entry), hash_algorithm)
            data.append(item)

        # ar.conf
        ar_path = path.join(common.SHARED_PATH, 'ar.conf')
        data.append({'filename': "ar.conf", 'hash': get_hash(ar_path, hash_algorithm)})
        data = process_array(data, search_text=search_text, search_in_fields=search_in_fields,
                             complementary_search=complementary_search, sort_by=sort_by,
                             sort_ascending=sort_ascending, offset=offset, limit=limit, q=q, select=select,
                             allowed_select_fields=GROUP_FILES_FIELDS, distinct=distinct,
                             required_fields=GROUP_FILES_REQUIRED_FIELDS)
        result.affected_items = data['items']
        result.total_affected_items = data['totalItems']
    except WazuhError as e:
        result.add_failed_item(id_=group_path, error=e)
        raise e
    except Exception as e:
        raise WazuhInternalError(1727, extra_message=str(e))

    return result


@expose_resources(actions=["group:create"], resources=["*:*:*"], post_proc_func=None)
def create_group(group_id: str) -> WazuhResult:
    """Creates a group.

    Parameters
    ----------
    group_id : str
        Group ID.

    Raises
    ------
    WazuhError(1722)
        If there was a validation error.
    WazuhError(1711)
        If the group already exists.
    WazuhError(1713)
        If the group ID is not valid.
    WazuhInternalError(1005)
        If there was an error reading a file.

    Returns
    -------
    WazuhResult
        WazuhResult object with a operation message.
    """
    # Input Validation of group_id
    if not InputValidator().group(group_id):
        raise WazuhError(1722)

    group_path = path.join(common.SHARED_PATH, group_id)

    if group_id.lower() == "default" or path.exists(group_path):
        if not path.isfile(group_path):
            raise WazuhError(1711, extra_message=group_id)
        else:
            raise WazuhError(1713, extra_message=group_id)

    # Create group in /etc/shared
    agent_conf_template = path.join(common.SHARED_PATH, 'agent-template.conf')
    try:
        mkdir_with_mode(group_path)
        full_copy(agent_conf_template, path.join(group_path, 'agent.conf'))

        chown_r(group_path, common.wazuh_uid(), common.wazuh_gid())
        chmod_r(group_path, 0o660)
        chmod(group_path, 0o700)
        msg = f"Group '{group_id}' created."
    except Exception as e:
        raise WazuhInternalError(1005, extra_message=str(e))

    return WazuhResult({'message': msg})


@expose_resources(actions=["group:delete"], resources=["group:id:{group_list}"],
                  post_proc_kwargs={'exclude_codes': [1710, 1712]})
def delete_groups(group_list: list = None) -> AffectedItemsWazuhResult:
    """Delete a list of groups and remove it from every agent assignations.

    Parameters
    ----------
    group_list : list
        List of Group names.

    Returns
    -------
    AffectedItemsWazuhResult
        Affected items.
    """
    result = AffectedItemsWazuhResult(all_msg='All selected groups were deleted',
                                      some_msg='Some groups were not deleted',
                                      none_msg='No group was deleted')

    system_groups = get_groups()
    for group_id in group_list:
        try:
            # Check if group exists
            if group_id not in system_groups:
                raise WazuhResourceNotFound(1710)
            elif group_id == 'default':
                raise WazuhError(1712)

            Agent.delete_single_group(group_id)
            result.affected_items.append(group_id)
        except WazuhException as e:
            result.add_failed_item(id_=group_id, error=e)

    result.total_affected_items = len(result.affected_items)

    return result


@expose_resources(actions=["group:modify_assignments"], resources=['group:id:{replace_list}'], post_proc_func=None)
@expose_resources(actions=["group:modify_assignments"], resources=['group:id:{group_list}'], post_proc_func=None)
@expose_resources(actions=["agent:modify_group"], resources=["agent:id:{agent_list}"],
                  post_proc_kwargs={'exclude_codes': [1701, 1703, 1751, 1752]})
def assign_agents_to_group(group_list: list = None, agent_list: list = None, replace: bool = False,
                           replace_list: list = None) -> AffectedItemsWazuhResult:
    """Assign a list of agents to a group.

    Parameters
    ----------
    group_list : list
        List with the group ID.
    agent_list : list
        List of Agent IDs.
    replace :  bool
        Whether to append new group to current agent's group or replace it.
    replace_list : list
        List of Group names that can be replaced.

    Raises
    ------
    WazuhResourceNotFound(1710)
        If the group was not found.


    Returns
    -------
    AffectedItemsWazuhResult
        Affected items.
    """
    group_id = group_list[0]
    result = AffectedItemsWazuhResult(all_msg=f'All selected agents were assigned to {group_id}'
                                              f'{" and removed from the other groups" if replace else ""}',
                                      some_msg=f'Some agents were not assigned to {group_id}'
                                               f'{" and removed from the other groups" if replace else ""}',
                                      none_msg='No agents were assigned to {0}'.format(group_id)
                                      )
    # Check if the group exists
    if not Agent.group_exists(group_id):
        raise WazuhResourceNotFound(1710)

    system_agents = get_agents_info()

    # Check agent '000'
    if '000' in agent_list:
        agent_list.remove('000')
        result.add_failed_item(id_='000', error=WazuhError(1703))

    agent_list = set(agent_list)

    # Check for non-existing agents
    not_found_agents = agent_list - system_agents
    for agent_id in not_found_agents:
        result.add_failed_item(id_=agent_id, error=WazuhResourceNotFound(1701))

    agent_list -= not_found_agents

    for agent_id in agent_list:
        try:
            Agent.add_group_to_agent(group_id, agent_id, replace=replace, replace_list=replace_list)
            result.affected_items.append(agent_id)
        except WazuhException as e:
            result.add_failed_item(id_=agent_id, error=e)

    result.total_affected_items = len(result.affected_items)
    result.affected_items.sort(key=int)

    return result


@expose_resources(actions=["group:modify_assignments"], resources=['group:id:{group_list}'], post_proc_func=None)
@expose_resources(actions=["agent:modify_group"], resources=['agent:id:{agent_list}'], post_proc_func=None)
def remove_agent_from_group(group_list: list = None, agent_list: list = None) -> WazuhResult:
    """Removes an agent assignation with a specified group.

    Parameters
    ----------
    group_list : list
        List with the group ID.
    agent_list : list
        List with the agent ID.

    Raises
    ------
    WazuhResourceNotFound(1701)
        Agent was not found.
    WazuhError(1703)
        Agent ID is 000.
    WazuhResourceNotFound(1710)
        Group was not found.

    Returns
    -------
    WazuhResult
        Confirmation message.
    """
    group_id = group_list[0]
    agent_id = agent_list[0]

    # Check if agent and group exist and it is not 000
    if agent_id not in get_agents_info():
        raise WazuhResourceNotFound(1701)
    if agent_id == '000':
        raise WazuhError(1703)
    if group_id not in get_groups():
        raise WazuhResourceNotFound(1710)

    return WazuhResult({'message': Agent.unset_single_group_agent(agent_id=agent_id, group_id=group_id, force=True)})


@expose_resources(actions=["agent:modify_group"], resources=["agent:id:{agent_list}"], post_proc_func=None)
@expose_resources(actions=["group:modify_assignments"], resources=["group:id:{group_list}"],
                  post_proc_kwargs={'exclude_codes': [1710, 1734, 1745]})
def remove_agent_from_groups(agent_list: list = None, group_list: list = None) -> AffectedItemsWazuhResult:
    """Removes an agent assignation with a list of groups.

    Parameters
    ----------
    group_list : list
        List of groups IDs.
    agent_list : list
        List with the agent ID.

    Raises
    ------
    WazuhResourceNotFound(1701)
        Agent was not found.
    WazuhError(1703)
        Agent ID is 000.

    Returns
    -------
    AffectedItemsWazuhResult
        Affected items.
    """
    agent_id = agent_list[0]
    result = AffectedItemsWazuhResult(all_msg='Specified agent was removed from returned groups',
                                      some_msg='Specified agent was not removed from some groups',
                                      none_msg='Specified agent was not removed from any group'
                                      )

    # Check if agent exists and it is not 000
    if agent_id == '000':
        raise WazuhError(1703)
    if agent_id not in get_agents_info():
        raise WazuhResourceNotFound(1701)

    # We move default group to last position in case it is contained in group_list. When an agent is removed from all
    # groups it is reverted to 'default'. We try default last to avoid removing it and then adding again.
    try:
        group_list.append(group_list.pop(group_list.index('default')))
    except ValueError:
        pass

    system_groups = get_groups()
    for group_id in group_list:
        try:
            if group_id not in system_groups:
                raise WazuhResourceNotFound(1710)
            Agent.unset_single_group_agent(agent_id=agent_id, group_id=group_id, force=True)
            result.affected_items.append(group_id)
        except WazuhException as e:
            result.add_failed_item(id_=group_id, error=e)
    result.total_affected_items = len(result.affected_items)
    result.affected_items.sort()

    return result


@expose_resources(actions=["group:modify_assignments"], resources=["group:id:{group_list}"], post_proc_func=None)
@expose_resources(actions=["agent:modify_group"], resources=["agent:id:{agent_list}"],
                  post_proc_kwargs={'exclude_codes': [1701, 1703, 1734]})
def remove_agents_from_group(agent_list: list = None, group_list: list = None) -> AffectedItemsWazuhResult:
    """Remove the assignations of a list of agents with a specified group.

    Parameters
    ----------
    group_list : list
        List with the group ID.
    agent_list : list
        List of Agent IDs.

    Raises
    ------
    WazuhResourceNotFound(1710)
        Group was not found.

    Returns
    -------
    AffectedItemsWazuhResult
        Affected items.
    """
    group_id = group_list[0]
    result = AffectedItemsWazuhResult(all_msg=f'All selected agents were removed from group {group_id}',
                                      some_msg=f'Some agents were not removed from group {group_id}',
                                      none_msg=f'No agent was removed from group {group_id}'
                                      )

    system_groups = get_groups()
    system_agents = get_agents_info()
    # Check if group exists
    if group_id not in system_groups:
        raise WazuhResourceNotFound(1710)

    for agent_id in agent_list:
        try:
            if agent_id == '000':
                raise WazuhError(1703)
            elif agent_id not in system_agents:
                raise WazuhResourceNotFound(1701)
            Agent.unset_single_group_agent(agent_id=agent_id, group_id=group_id, force=True)
            result.affected_items.append(agent_id)
        except WazuhException as e:
            result.add_failed_item(id_=agent_id, error=e)
    result.total_affected_items = len(result.affected_items)
    result.affected_items.sort(key=int)

    return result


@expose_resources(actions=["agent:read"], resources=["agent:id:{agent_list}"], post_proc_func=None)
def get_outdated_agents(agent_list: list = None, offset: int = 0, limit: int = common.DATABASE_LIMIT, sort: dict = None,
                        search: dict = None, select: str = None, q: str = None) -> AffectedItemsWazuhResult:
    """Gets the outdated agents.

    Parameters
    ----------
    agent_list : list
        List of agents ID's.
    offset : int
        First item to return.
    limit : int
        Maximum number of items to return. Default: common.DATABASE_LIMIT
    sort : dict
        Sorts the items. Format: {"fields":["field1","field2"],"order":"asc|desc"}.
    search : dict
        Looks for items with the specified string. Format: {"fields": ["field1","field2"]}.
    select : str
        Select which fields to return (separated by comma).
    q : str
        Query to filter results by. For example q&#x3D;&amp;quot;status&#x3D;active&amp;quot;

    Returns
    -------
    AffectedItemsWazuhResult
        Affected items.
    """

    result = AffectedItemsWazuhResult(all_msg='All selected agents information was returned',
                                      some_msg='Some agents information was not returned',
                                      none_msg='No agent information was returned'
                                      )
    if agent_list:
        # Get manager version
        manager = Agent(id='000')
        manager.load_info_from_db()

        rbac_filters = get_rbac_filters(system_resources=get_agents_info(), permitted_resources=agent_list)

        with WazuhDBQueryAgents(offset=offset, limit=limit, sort=sort, search=search, select=select,
                                query=f"version!={manager.version}" + (f";({q})" if q else ''),
                                **rbac_filters) as db_query:
            data = db_query.run()

        result.affected_items = data['items']
        result.total_affected_items = data['totalItems']

    return result


@expose_resources(actions=["agent:upgrade"], resources=["agent:id:{agent_list}"],
                  post_proc_kwargs={'exclude_codes': [1701, 1703, 1707, 1731] + ERROR_CODES_UPGRADE_SOCKET})
def upgrade_agents(agent_list: list = None, wpk_repo: str = None, version: str = None, force: bool = False,
                   use_http: bool = False, package_type: str = None, file_path: str = None, installer: str = None,
                   filters: dict = None, q: str = None) -> AffectedItemsWazuhResult:
    """Start the agent upgrade process.

    Parameters
    ----------
    agent_list : list
        List of agents ID's.
    wpk_repo : str
        URL for WPK download.
    version : str
        Version to upgrade to.
    force : bool
        Forces the agents to upgrade, ignoring version validations.
    use_http : bool
        False for HTTPS protocol, True for HTTP protocol.
    package_type : str
        Default package type (rpm, deb).
    file_path : str
        Path to the installation file.
    installer : str
        Selected installer.
    filters : dict
        Define required field filters. Format: {"field1":"value1", "field2":["value2","value3"]}
    q : str
        Define query to filter in DB.

    Raises
    ------
    WazuhError(1823)
        If the target version is higher than the manager's.
    WazuhInternalError
        Generic internal server error.

    Returns
    -------
    AffectedItemsWazuhResult
        Result with IDs of created tasks.
    """
    result = AffectedItemsWazuhResult(all_msg='All upgrade tasks were created',
                                      some_msg='Some upgrade tasks were not created',
                                      none_msg='No upgrade task was created',
                                      sort_fields=['agent'], sort_ascending='True')

    if agent_list:
        system_agents = get_agents_info()
        rbac_filters = get_rbac_filters(system_resources=system_agents, permitted_resources=agent_list,
                                        filters=filters)

        with WazuhDBQueryAgents(limit=None, select=["id", "status"], query=q, **rbac_filters) as db_query:
            data = db_query.run()

        filtered_agents = set([agent['id'] for agent in data['items']])
        agent_list = set(agent_list)

        try:
            agent_list.remove('000')
            result.add_failed_item('000', WazuhError(1703))
        except KeyError:
            pass

        # Add non existent agents to failed_items
        not_found_agents = agent_list - system_agents
        [result.add_failed_item(id_=agent, error=WazuhResourceNotFound(1701)) for agent in not_found_agents]

        # Add non active agents to failed_items
        non_active_agents = [agent['id'] for agent in data['items'] if agent['status'] != 'active']
        [result.add_failed_item(id_=agent, error=WazuhError(1707)) for agent in non_active_agents]
        non_active_agents = set(non_active_agents)

        # Add non eligible agents to failed_items
        non_eligible_agents = agent_list - not_found_agents - non_active_agents - filtered_agents
        [result.add_failed_item(id_=ag, error=WazuhError(
            1731,
            extra_message="some of the requirements are not met -> {}".format(
                ', '.join(f"{key}: {value}" for key, value in filters.items() if key != 'rbac_ids') +
                (f', q: {q}' if q else '')
            )
        )) for ag in non_eligible_agents]

        eligible_agents = agent_list - not_found_agents - non_active_agents - non_eligible_agents

        # Transform the format of the agent ids to the general format
        eligible_agents = [int(agent) for agent in eligible_agents]

        tasks_results = create_upgrade_tasks(eligible_agents=eligible_agents, chunk_size=UPGRADE_CHUNK_SIZE,
                                             command='upgrade' if not (installer or file_path) else 'upgrade_custom',
                                             wpk_repo=wpk_repo, version=version, force=force, use_http=use_http,
                                             package_type=package_type, file_path=file_path, installer=installer)

        for agent_result_chunk in tasks_results:
            for agent_result in agent_result_chunk['data']:
                socket_error = agent_result['error']
                # Success, return agent and task IDs
                if socket_error == 0:
                    task_agent = {
                        'agent': str(agent_result['agent']).zfill(3),
                        'task_id': agent_result['task_id']
                    }
                    result.affected_items.append(task_agent)
                    result.total_affected_items += 1

                # Upgrade error for specific agents
                elif (error_code := 1810 + socket_error) in ERROR_CODES_UPGRADE_SOCKET:
                    error = WazuhError(error_code, cmd_error=True, extra_message=agent_result['message'])
                    result.add_failed_item(id_=str(agent_result['agent']).zfill(3), error=error)

                # Upgrade error for all agents, bad request
                elif error_code in ERROR_CODES_UPGRADE_SOCKET_BAD_REQUEST:
                    raise WazuhError(error_code, cmd_error=True, extra_message=agent_result['message'])

                # Upgrade error for all agents, internal server error
                else:
                    raise WazuhInternalError(error_code, cmd_error=True, extra_message=agent_result['message'])

    result.affected_items.sort(key=operator.itemgetter('agent'))

    return result


@expose_resources(actions=["agent:upgrade"], resources=["agent:id:{agent_list}"],
                  post_proc_kwargs=
                  {'exclude_codes': [1701, 1703, 1707, 1731] + ERROR_CODES_UPGRADE_SOCKET_GET_UPGRADE_RESULT})
def get_upgrade_result(agent_list: list = None, filters: dict = None, q: str = None) -> AffectedItemsWazuhResult:
    """Read upgrade result output from agent.

    Parameters
    ----------
    agent_list : list
        List of agent ID's.
    filters : dict
        Define required field filters. Format: {"field1":"value1", "field2":["value2","value3"]}
    q : str
        Define query to filter in DB.

    Raises
    ------
    WazuhInternalError
        Generic internal server error.

    Returns
    -------
    AffectedItemsWazuhResult
        Upgrade results.
    """
    result = AffectedItemsWazuhResult(all_msg='All upgrade tasks were returned',
                                      some_msg='Some upgrade tasks were not returned',
                                      none_msg='No upgrade task was returned')

    if agent_list:
        system_agents = get_agents_info()
        rbac_filters = get_rbac_filters(system_resources=system_agents, permitted_resources=agent_list,
                                        filters=filters)

        with WazuhDBQueryAgents(limit=None, select=["id", "status"], query=q, **rbac_filters) as db_query:
            data = db_query.run()

        filtered_agents = set([agent['id'] for agent in data['items']])
        agent_list = set(agent_list)

        try:
            agent_list.remove('000')
            result.add_failed_item('000', WazuhError(1703))
        except KeyError:
            pass

        # Add non existent agents to failed_items
        not_found_agents = agent_list - system_agents
        [result.add_failed_item(id_=agent, error=WazuhResourceNotFound(1701)) for agent in not_found_agents]

        # Add non active agents to failed_items
        non_active_agents = [agent['id'] for agent in data['items'] if agent['status'] != 'active']
        [result.add_failed_item(id_=agent, error=WazuhError(1707)) for agent in non_active_agents]
        non_active_agents = set(non_active_agents)

        # Add non eligible agents to failed_items
        non_eligible_agents = agent_list - not_found_agents - non_active_agents - filtered_agents
        [result.add_failed_item(id_=ag, error=WazuhError(
            1731,
            extra_message="some of the requirements are not met -> {}".format(
                ', '.join(f"{key}: {value}" for key, value in filters.items() if key != 'rbac_ids') +
                (f', q: {q}' if q else '')
            )
        )) for ag in non_eligible_agents]

        eligible_agents = agent_list - not_found_agents - non_active_agents - non_eligible_agents

        # Transform the format of the agent ids to the general format
        eligible_agents = [int(agent) for agent in eligible_agents]

        task_results = create_upgrade_tasks(eligible_agents=eligible_agents, chunk_size=UPGRADE_RESULT_CHUNK_SIZE,
                                            command='upgrade_result', get_result=True)

        for task_result_chunk in task_results:
            for task_result in task_result_chunk['data']:
                task_error = task_result.pop('error')
                # Success, return agent and task IDs
                if task_error == 0:
                    task_result['agent'] = str(task_result['agent']).zfill(3)
                    result.affected_items.append(task_result)
                    result.total_affected_items += 1

                # Upgrade error for specific agents (no task in DB)
                elif (error_code := 1810 + task_error) in ERROR_CODES_UPGRADE_SOCKET_GET_UPGRADE_RESULT:
                    error = WazuhError(error_code, cmd_error=True, extra_message=task_result['message'])
                    result.add_failed_item(id_=str(task_result['agent']).zfill(3), error=error)

                # Upgrade error for all agents, internal server error
                else:
                    raise WazuhInternalError(error_code, cmd_error=True, extra_message=task_result['message'])

    result.affected_items.sort(key=operator.itemgetter('agent'))

    return result


@expose_resources(actions=["agent:read"], resources=["agent:id:{agent_list}"], post_proc_func=None)
def get_agent_config(agent_list: list = None, component: str = None, config: str = None) -> WazuhResult:
    """Read selected configuration from agent.

    Parameters
    ----------
    agent_list : list
        List of agents ID's.
    component : str
        Selected component.
    config : str
        Configuration to get, written on disk.

    Raises
    ------
    WazuhError(1740)
        If the agent is not active.

    Returns
    -------
    WazuhResult
        Loaded configuration in JSON.
    """
    # We access unique agent_id from list, this may change if and when we decide a final way to handle get responses
    # with failed ids and a list of agents
    agent_id = agent_list[0]
    my_agent = Agent(agent_id)
    my_agent.load_info_from_db()

    if my_agent.status != "active":
        raise WazuhError(1740)

    return WazuhResult(
        {'data': my_agent.get_config(component=component, config=config, agent_version=my_agent.version)})


@expose_resources(actions=["agent:read"], resources=["agent:id:{agent_list}"],
                  post_proc_kwargs={'exclude_codes': [1701, 1703]})
def get_agents_sync_group(agent_list: list = None) -> AffectedItemsWazuhResult:
    """Get agents configuration sync status.

    Notes
    -----
    To be deprecated in v5.0.

    Parameters
    ----------
    agent_list : list
        List of agent IDs.

    Returns
    -------
    AffectedItemsWazuhResult
        Agent group sync status information.
    """
    result = AffectedItemsWazuhResult(all_msg='Sync info was returned for all selected agents',
                                      some_msg='Sync info was not returned for some selected agents',
                                      none_msg='No sync info was returned',
                                      )

    system_agents = get_agents_info()
    for agent_id in agent_list:
        try:
            if agent_id == "000":
                raise WazuhError(1703)
            if agent_id not in system_agents:
                raise WazuhResourceNotFound(1701)
            else:
                # Check if agent exists and it is active
                agent_info = Agent(agent_id).get_basic_information()
                # Check if it has a multigroup
                if len(agent_info['group']) > 1:
                    multi_group = ','.join(agent_info['group'])
                    multi_group = hashlib.sha256(multi_group.encode()).hexdigest()[:8]
                    group_merged_path = path.join(common.MULTI_GROUPS_PATH, multi_group, "merged.mg")
                else:
                    group_merged_path = path.join(common.SHARED_PATH, agent_info['group'][0], "merged.mg")
                result.affected_items.append({'id': agent_id,
                                              'synced': md5(group_merged_path) == agent_info['mergedSum']})
        except (IOError, KeyError):
            # The file couldn't be opened and therefore the group has not been synced
            result.affected_items.append({'id': agent_id, 'synced': False})
        except WazuhException as e:
            result.add_failed_item(id_=agent_id, error=e)

    result.total_affected_items = len(result.affected_items)
    result.affected_items.sort(key=lambda i: i['id'])

    return result


@expose_resources(actions=["group:read"], resources=["group:id:{group_list}"], post_proc_func=None)
def get_file_conf(group_list: list = None, type_conf: str = None, raw: bool = False,
                  filename: str = None) -> WazuhResult:
    """Read configuration file for a specified group.

    Parameters
    ----------
    group_list : list
        List with the group ID.
    type_conf : str
        Type of file.
    raw : bool
        Respond in raw format.
    filename : str
        Filename to read config from.

    Returns
    -------
    WazuhResult
        WazuhResult object with the configuration.
    """
    # We access unique group_id from list, this may change if and when we decide to add option to get configuration
    # files for a list of groups
    group_id = group_list[0]

    return WazuhResult({'data': configuration.get_file_conf(filename, group_id=group_id, type_conf=type_conf,
                                                            raw=raw)})


@expose_resources(actions=["group:read"], resources=["group:id:{group_list}"], post_proc_func=None)
def get_agent_conf(group_list: list = None, filename: str = 'agent.conf', offset: int = 0,
                   limit: int = common.DATABASE_LIMIT) -> WazuhResult:
    """Read agent conf for a specified group.

    Parameters
    ----------
    group_list : list
        List with the group ID.
    filename : str
        Filename to read config from. Default: 'agent.conf'
    offset : int
        First item to return.
    limit : int
        Maximum number of items to return. Default: common.DATABASE_LIMIT

    Returns
    -------
    WazuhResult
        WazuhResult object with the configuration.
    """
    # We access unique group_id from list, this may change if and when we decide to add option to get agent conf for
    # a list of groups
    group_id = group_list[0]

    return WazuhResult(
        {'data': configuration.get_agent_conf(group_id=group_id, filename=filename, offset=offset, limit=limit)})


@expose_resources(actions=["group:update_config"], resources=["group:id:{group_list}"], post_proc_func=None)
def upload_group_file(group_list: list = None, file_data: str = None, file_name: str = 'agent.conf') -> WazuhResult:
    """Update a group file.

    Parameters
    ----------
    group_list : list
        List with the group ID.
    file_data : str
        Relative path of temporary file to upload.
    file_name : str
        Name of the file to update. Default: 'agent.conf'

    Returns
    -------
    WazuhResult
        WazuhResult object with the confirmation message.
    """
    # We access unique group_id from list, this may change if and when we decide to add option to update files for
    # a list of groups
    group_id = group_list[0]

    return WazuhResult({'message': configuration.upload_group_file(group_id, file_data, file_name=file_name)})


def get_full_overview() -> WazuhResult:
    """Get information about agents.

    Returns
    -------
    WazuhResult
        WazuhResult object with information about agents.
    """
    # We don't consider agent 000 in order to get the summary
    q = "id!=000"

    # Get information from different methods of Agent class
    stats_distinct_node = get_distinct_agents(fields=['node_name'], q=q).affected_items
    groups = get_agent_groups().affected_items
    stats_distinct_os = get_distinct_agents(fields=['os.name',
                                                    'os.platform', 'os.version'], q=q).affected_items
    stats_version = get_distinct_agents(fields=['version'], q=q).affected_items
    agent_summary_status = get_agents_summary_status()
    summary = agent_summary_status['data'] if 'data' in agent_summary_status else dict()
    try:
        last_registered_agent = [get_agents(limit=1,
                                            sort={'fields': ['dateAdd'], 'order': 'desc'},
                                            q=q).affected_items[0]]
    except IndexError:  # an IndexError could happen if there are not registered agents
        last_registered_agent = []
    # combine results in an unique dictionary
    result = {'nodes': stats_distinct_node, 'groups': groups, 'agent_os': stats_distinct_os, 'agent_status': summary,
              'agent_version': stats_version, 'last_registered_agent': last_registered_agent}

    return WazuhResult({'data': result})


@expose_resources(actions=["agent:uninstall"], resources=["*:*:*"], post_proc_func=None)
def check_uninstall_permission() -> WazuhResult:
    """Check if the user has permission to uninstall agents.

    This function verifies whether the user has the necessary permissions
    to uninstall agents by checking the assigned RBAC actions and resources.

    Returns
    -------
    WazuhResult
        WazuhResult object containing a message indicating if the
        user has the permission to uninstall agents.
    """
    return WazuhResult({'message': 'User has permission to uninstall agents'})
